#include "ring_thread_list.h"
#include <ucontext.h>
#include <stdlib.h>
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
#include <sys/time.h>
#include <assert.h>
#include <valgrind/valgrind.h>
#include <preemption/preemption.h>
#include "thread.h"
#define STACKSIZE 64*1024
#define TEMPPREEMTION 30000

struct list* sleepq=NULL;
struct list* runq=NULL;
struct list* deadq=NULL;
struct thread * current;


void add_thread_runq(thread_t thread){
  insert(runq,thread);
}
/**
 * Supprime un thread
 * @param thread le thread à supprimer de la memoire
 * return un pointeur sur le thread désalloue de la memoire
 * Attention! ne pas essayer d'acceder à ce thread
 */
struct thread* delete_thread(struct thread * thread){
	if(thread){
		if(thread->context.uc_stack.ss_sp)
			free(thread->context.uc_stack.ss_sp);
		if(thread->execution_time)
			free(thread->execution_time);
		free(thread);
	}
	return NULL;
}
/**
 * Libere la memoire allouée pour les threads à la fin de l'execution du programme
 *
 *
 */
void end_main_thread(){
	fprintf(stderr,"freeing memory\n");
	struct thread* tmp;
	runq=free_list(runq);
	deadq=free_list(deadq);
	sleepq=free_list(sleepq);
}

/* initialise un contexte et lui alloue de la memoire
 * @param uc contexte à initialiser
 * @param ouc contexte mis dans le uc link du premier contexte ce contexte sera
 * remis si le premier se termine (sans faire de setcontext)
 * @return contexte initialisé
 */
int context_init(ucontext_t * uc,ucontext_t * ouc) {
	getcontext(uc); //juste pour initialiser
	uc->uc_stack.ss_size = STACKSIZE;
	uc->uc_stack.ss_sp = malloc(uc->uc_stack.ss_size); //Allocation de la pile
	if(!uc->uc_stack.ss_sp){
		fprintf(stderr,"depassement de capacité mémoire\n");
		exit(0);
	}
	int valgrind_stackid = VALGRIND_STACK_REGISTER(uc->uc_stack.ss_sp,
	                                               uc->uc_stack.ss_sp + uc->uc_stack.ss_size);
	uc->uc_stack.ss_flags = 0;
	uc->uc_link = ouc;
	return valgrind_stackid;
}

/**
 * initialise le thread courant et le place sur la runq. Elle permet de gérer le main comme un thread quelconque
 */
void thread_init(){
	current =malloc(sizeof(struct thread));
	current->execution_time=NULL;
	current->status=RUNNING;
	current->thread_id=1;
	current->priority=1;
	current->retval=NULL;//TODO Ajouter la gestion des erreurs grace a la valeur de retour du main
	runq= create(runq,current);
	atexit(end_main_thread);
	current->valgrind_stackid=context_init(&(current->context),NULL);
}
thread_t thread_self(){
	return current;//==NULL ? -1 : current->thread_id;
}

/* Fonction tampon utilisée pour récuperer la valeur
 * de retour d'un thread en le forçant à passer par thread_exit()
 * @param f fonction du thread
 * @param parametres de cette fonction
 *
 */


void handler (int numsig, siginfo_t* sig, void* p){
	thread_yield();	
}

void buffer_function(void * (*f) (void *), void * funcarg) {
	assert(NULL != f);// NULL != funcarg);
	static sigset_t preempt_set;
	sigprocmask(SIG_UNBLOCK,&preempt_set,NULL);
	create_sigaction((void(*)(int,siginfo_t*,void*)) handler,&preempt_set);  /* Ajout de l'appel à create_sigaction (preemption)*/
	timer(current->priority*TEMPPREEMTION); /* Ajout du timer */
	void * retval= f(funcarg);
	thread_exit(retval);
	
}    

/** creer un nouveau thread qui va exécuter la fonction func avec l'argument funcarg.
 *
 * La fonction makecontext() modifie le contexte pointé par ucp (qui a été
 *     obtenu par un appel à getcontext(2)).  Avant  d'appeler  makecontext(),
 *     l'appelant   doit  allouer  une  nouvelle  pile  pour  ce  contexte  et
 *     l'affecter  à  ucp->uc_stack  et  définir  un  contexte  successeur  et
 *     l'affecter à ucp->uc_link.
 *@return renvoie 0 en cas de succès, -1 en cas d'erreur.
 *
 */

int thread_create(thread_t *newthread,void *(*func)(void *), void *funcarg) {
	int i=0;
	if(current == NULL)
		thread_init();
	if(func==NULL)
		return -1;
	struct thread * new = malloc(sizeof(struct thread));
	new->execution_time=NULL;
	new->valgrind_stackid=context_init(&new->context,&current->context);
	new->status = RUNNING;
	new->priority=1;
	new->thread_id=current->thread_id;
	current->thread_id++;
	makecontext(&new->context, (void (*)(void))buffer_function, 2, func, funcarg);
	*newthread = new;
	runq=insert(runq,new);
	i=thread_yield();
	return i;
}

int priority_thread_create(thread_t *newthread, int priority,void *(*func)(void *), void *funcarg) {
	int i=0;
	if(current == NULL)
		thread_init();
	if(func==NULL)
		return -1;
	struct thread * new = malloc(sizeof(struct thread));
	new->execution_time=NULL;
	new->valgrind_stackid=context_init(&new->context,&current->context);
	new->status = RUNNING;
	new->priority=priority;
	new->thread_id=current->thread_id;
	current->thread_id++;
	makecontext(&new->context, (void (*)(void))buffer_function, 2, func, funcarg);
	*newthread = new;
	runq=insert(runq,new);
	i=thread_yield();
	return i;
}
/** attendre la fin d'exécution d'un thread.
 * @param retval la valeur renvoyée par le thread est placée dans *retval.
 * si retval est NULL, la valeur de retour est ignorée.
 * @param thread thread qui sera attendu par le thread appelant cette fonction
 * @return renvoie 0 en cas de succès, -1 si il y a eu un probleme lors de l'attente ou de la récupération de
 * la valeur de retour
 */
inline int thread_join(thread_t thread, void **retval){
	int i=0;
	if(thread == NULL||current==NULL)
		return -1;
	while(!i&&thread->status != ENDED)
		i=thread_yield();
	if(retval != NULL)
		*retval = thread->retval;
	return i;
}
/** attendre la fin d'exécution d'un thread.
 * @param retval la valeur renvoyée par le thread est placée dans *retval.
 * si retval est NULL, la valeur de retour est ignorée.
 * @param thread thread qui sera attendu par le thread appelant cette fonction
 * @return renvoie 0 en cas de succès, -1 si il y a eu un probleme lors de l'attente ou de la récupération de
 * la valeur de retour
 * Contrairement à thread_join(), le thread est supprimé à la fin de cette fontion
 */
int final_thread_join(thread_t thread,void** retval){
	int i=0;
	i=thread_join(thread,retval);
	deadq=remove_element(deadq,thread);
	thread=delete_thread(thread);
	return i;
}
/** passer la main à un autre thread.
 * @return renvoie 0 en cas de succès,-1 si le changement de contexte n'a pas pu etre fini.
 */
int thread_yield(){
	int i=0;
	struct thread * previous = current;
	getcontext(&previous->context);
	if(current==NULL)
		thread_init();
	else if(size(runq)== 1&&head(runq)==current)
		return 0;
	else{
		runq=shift(runq);
		current = head(runq);
		if(previous->status==ENDED)
			i=setcontext(&((head(runq))->context));
		else
			i=swapcontext(&previous->context, &current->context);
	}
	return i;
}

/** terminer le thread courant en renvoyant la valeur de retour retval.
 * cette fonction ne retourne jamais.
 *
 * L'attribut noreturn aide le compilateur à optimiser le code de
 * l'application (élimination de code mort). Attention à ne pas mettre
 * cet attribut dans votre interface tant que votre thread_exit()
 * n'est pas correctement implémenté (il ne doit jamais retourner).
 * @param retval valeur de retour du thread qui va terminer
 */


void thread_exit(void *retval){
	if(size(runq) != 1){
		current->status = ENDED;
		if(retval != NULL)
			current->retval = retval;
		runq=remove_element(runq,current);
		deadq=insert(deadq,current);
		thread_yield();
		exit(0);
	}

	else {
		exit(0);
	}
}



